using System;
using System.Collections;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Xml;

using MFWebSockets.Core.Utilities;
using Microsoft.SPOT;

namespace MFWebSockets.Core.Clients
{
    public class PusherClient //: IWebSocketClient
    {
        public enum ConnectionStatuses
        {
            Disconnected,
            Connected,
            Subscribed,
            Vacated
        }

        private readonly Socket socket;
        private readonly string appKey;
        private readonly string channel;
        private EndPoint endpoint;

        public PusherClient(string appKey, string channel = "default")
        {
            this.appKey = appKey;
            this.channel = channel;
            ConnectionStatus = ConnectionStatuses.Disconnected;

            var endpointUri = new Uri("ws://ws.pusherapp.com:80/app/" + appKey + "?client=js&version=1.8.5");

            var pos = endpointUri.AbsoluteUri.IndexOf('/', 5);
            var hostInfo = endpointUri.AbsoluteUri.Substring(5, pos - 5).Split(':');
            int port;
            try
            {
                port = int.Parse(hostInfo[1]);
            }
            catch
            {
                throw new ArgumentException("Invalid websocket address!");
            }
            var host = hostInfo[0];
            try
            {
                var ipAddress = IPAddress.Parse(host);
                endpoint = new IPEndPoint(ipAddress, port);
            }
            catch (ArgumentException)
            {
                try
                {
                    var hostEntry = Dns.GetHostEntry(host);
                    
                    endpoint = new IPEndPoint(hostEntry.AddressList[0], port);
                }
                catch(Exception exception)
                {
                    throw new ArgumentException("Could not resolve host via DNS.", exception);
                }
            }
            catch (ThreadAbortException)
            {
                return;
            }

            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
                         {
                             SendTimeout = 5000,
                             ReceiveTimeout = 5000
                         };


            Connected += OnConnected;
            Disconnected += OnDisconnected;
            Subscribed += OnSubscribed;
            Vacated += OnVacated;
        }

        void OnVacated(SubscriptionArgs arg)
        {
            ConnectionStatus = ConnectionStatuses.Vacated;
        }

        void OnSubscribed(SubscriptionArgs args)
        {
            ConnectionStatus = ConnectionStatuses.Subscribed;
        }

        void OnDisconnected(ConnectionArgs args)
        {
            ConnectionStatus = ConnectionStatuses.Disconnected;
        }

        void OnConnected(ConnectionArgs args)
        {
            ConnectionStatus = ConnectionStatuses.Connected;
        }

        public void Connect()
        {
            new Thread(() =>
                           {
                               socket.Connect(endpoint);
                               var headerBuilder = new StringBuilder();
                               headerBuilder.AppendLine(@"GET /app/" + appKey + @"?client=js&version=1.8.5 HTTP/1.1");
                               headerBuilder.Append(
                                   @"Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Key2: 1  0  IA DM j2
Host: ws.pusherapp.com
Sec-WebSocket-Key1:  _411M 01h\3 40
Origin: .Net Micro Framework

0s`VN1 3");
                               socket.SendTo(Encoding.UTF8.GetBytes(headerBuilder.ToString()), endpoint);
                               var buffer = new byte[1024];
                               socket.ReceiveFrom(buffer, SocketFlags.None, ref endpoint);
                               const string json =
                                   "{\"event\": \"pusher:subscribe\",\"data\": {\"channel\": \"default\" }}";
                               var buff = new Byte[json.Length + 2];
                               var x = Encoding.UTF8.GetBytes(json);
                               for (int i = 0; i < x.Length; i++)
                                   buff[i + 1] = x[i];
                               buff[0] = 0x00;
                               buff[x.Length + 1] = 0xFF;
                               socket.SendTo(buff, endpoint);
                               while (true)
                                   Receive();
                           }).Start();
        }

        private void Receive()
        {
            while(socket.Available > 0)
            {
                var buffer = new byte[512];
                socket.ReceiveFrom(buffer, SocketFlags.None, ref endpoint);

                var response = Tools.GetString(buffer, 0, 512);
                var xresp = response.Substring(0, response.Length - 1);



                const string dataStart = "{\"event\":\"default\",\"data\":\"";
                const string dataEnd = "\",\"channel\":\"default\"}";

                //const string dataStart = "\",\"data\":\"";
                //const string dataEnd = "\",\"channel\":\"default\"}";


                var length = xresp.IndexOf(dataEnd) - dataStart.Length;
                if (length > 0)
                {
                    var data = xresp.Substring(dataStart.Length, length);

                    if (PusherEvent != null)
                        PusherEvent(new PusherEventArgs
                                        {
                                            Name = "TwitterEvent",
                                            Data = data
                                        });
                }  
            }
        }




        public void Disconnect()
        {
            socket.Close();
        }


        public ConnectionStatuses ConnectionStatus { get; set; }

        public event ConnectedHandler Connected;
        public event DisconnectedHandler Disconnected;
        public event SubscribedHandler Subscribed;
        public event VacatedHandler Vacated;
        public event PusherEventHandler PusherEvent;

        public delegate void ConnectedHandler(ConnectionArgs args);
        public delegate void DisconnectedHandler(ConnectionArgs args);
        public delegate void SubscribedHandler(SubscriptionArgs args);
        public delegate void VacatedHandler(SubscriptionArgs arg);
        public delegate void PusherEventHandler(PusherEventArgs args);

        public class PusherEventArgs : EventArgs
        {
            public string Name;
            public string Data;
        }

        public class ConnectionArgs : EventArgs
        {
            public string SocketId;
        }

        public class SubscriptionArgs : EventArgs
        {
            public string Channel;
        }
        
    }

    public class TwitterClient : PusherClient
    {
        private readonly IEventDeserializer deserializer;

        public TwitterClient(string appKey, string channel, IEventDeserializer deserializer) : base(appKey, channel)
        {
            this.deserializer = deserializer;
            PusherEvent += OnPusherEvent;
        }

        void OnPusherEvent(PusherEventArgs args)
        {
            var obj = deserializer.Deserialize(args);
            
        }

        public event TweetHandler Tweet;

        public delegate void TweetHandler(TwitterEventArgs args);

        public class TwitterEventArgs
        {
            public DateTime Time;
            public string User;
            public string Tweet;
        }
    }

    public interface IEventDeserializer
    {
        object Deserialize(PusherClient.PusherEventArgs args);
    }

    public class XmlEventDeserializer : IEventDeserializer
    {
        public object Deserialize(PusherClient.PusherEventArgs args)
        {
            var xmlBuilder = new StringBuilder();
            CleanXml(args.Data, xmlBuilder);
            var cleanXml = xmlBuilder.ToString();
            var xmlBuffer = Encoding.UTF8.GetBytes(cleanXml);
            var obj = ReadObject(XmlReader.Create(new MemoryStream(xmlBuffer)), "TwitterEventArgs");
            return obj;
        }

        public void CleanXml(string xml, StringBuilder sb)
        {
            var i = xml.IndexOf("\\\"");
            if (i > 0)
            {
                var left = xml.Substring(0, i);
                var right = xml.Substring(i + 1);
                sb.Append(left);
                CleanXml(right, sb);
            }
            else
                sb.Append(xml);
        }

        private object ReadObject(XmlReader reader, string eventTypeName)
        {
            Type eventType = null;
            foreach (var type in GetType().Assembly.GetTypes())
                if (type.Name == eventTypeName)
                    eventType = type;
            if (eventType == null)
                throw new ArgumentException("Could not find type of: " + eventTypeName);

            var instance = AppDomain.CurrentDomain.CreateInstanceAndUnwrap(GetType().Assembly.FullName,
                                                                           eventType.FullName);
            reader.Read();
            if (reader.HasAttributes)
            {
                foreach (var field in eventType.GetFields())
                {
                    for (int i = 0; i < reader.AttributeCount; i++)
                    {
                        try
                        {
                            var value = reader.GetAttribute(field.Name);
                            if (value != null)
                                field.SetValue(instance, value);
                        }
                        catch (Exception)
                        {

                            throw;
                        }
                    }

                }
            }
            return instance;

        }
    }



}
